Back to all blogs
Share this blog via
Published on Thursday, December 28th 2023

Using Notion as a CMS

I’ve been wanting to set up a blog for the longest time now. But, I’ve been quite busy with work and couldn’t take the time to build it. So finally it was time for Christmas and I managed to get some freedom to work on this as well as my website and here’s how it went.

The website was pretty straightforward, I just built it using Next.js, TypeScript (cause, of course TS), Tailwind (it’s such a blessing ❤️) and Framer for some cool little animations. For the blog however, I needed a CMS because I of course didn’t want to go back and edit my code for every blogpost I end up creating, moreover, I was afraid I just wouldn’t write any posts because it would have been tedious without a CMS, so yes, CMS was a no brainer.

Also, I’m using a monorepo setup for my website and blog (and some other things that’ll come along soon), so that’s PNPM with Turborepo.

Why Notion?

I use Notion everyday for all of my note taking and it just made sense for me to be able to add a simple Notion page and have my blog use that instead of having to manage a CMS to create and edit blogposts. I also want to mention that, Notion already allows users to just make their pages public for the world to see, but I just wanted it to be via my website, plus, I wanted to have my own styling so my portfolio and blog could look and feel the same.

Getting started

Before I could have this wonderful blog, there were a few pieces of the puzzle that I had to gather.

  • Firstly, an API to retrieve my notion pages, so in this case, I am using the Notion JavaScript SDK, it’s pretty awesome, I’ve used it in the past too and would recommend it to anyone interested in a heartbeat.
  • Secondly, I needed a backend to call the Notion APIs (via the SDK of course) because of CORS and for that, I’m using the Next.js server components but you can also use Netlify’s Functions along with their CLI or something similar if you have a simple React app.

Trivia time: did you know that the Netlify CLI makes it super easy (and barely an inconvenience - if you know you know) to inject your environment variables into your development environment via the netlify dev command?

  • Lastly, I needed a way to convert the Notion blocks (i.e. their API response) to JSX. This, however, was a bit tricky, because I only found 2 npm packages that did it and they didn’t have all the flexibility that I needed for my usecase. So, that’s when I decided to build my own Notion to JSX parser.

Behold, notion-jsx (here’s the npm package). A simple library that will convert your Notion page blocks to JSX. It currently supports most of the block types (this blog is a quick demonstration of its capabilities 😉), so whatever you’re seeing on this webpage is coming from my Notion page and then being parsed by notion-jsx to render these beautiful, beautiful components. And yes! Did I mention, it’s completely free and open source so, please feel free to step in and contribute in making it even better.

Diving into the code

Before we hop in though, if you stuck with me so far, thank you! I really appreciate you reading my very first blogpost (on my blog, at least) and hope you’ll keep reading more of my blogs as I keep posting them. I know, we’re missing a mailing list 🥲, but more on that soon. Okay, now let me show you how easy it is to have your own blog that is powered by Notion.

First, you’ll need a super simple React app (can be a meta framework like Next.js, Preact, Remix or anything that uses JSX and React under the hood). You’ll also need some kind of a backend, so you can either go serverless like Netlify Functions, Firebase Cloud Functions, Cloudflare workers or simply use NextJS / Remix since they will allow you to have server side logic via server components, and that’ll help you bypass CORS troubles. Then, you can install the Notion JavaScript SDK. If you want to use the APIs instead of the SDK, you can check out the Notion API’s Workspace on Postman.

bash
 npm install @notionhq/client

In the meantime, on Notion’s Developer API website, you’ll need to set up an integration that will allow your app / server to fetch the Notion pages. You can follow the guide here to do that easily. Now let’s actually step into Notion and set up a little database that can hold some important stuff for us. Let’s add the following columns to this table.

  • name - the name of your blogpost
  • tags - if you want to add some tags to associate with the blog. In my case, I use them to decide which notion pages to show on this website.
  • imageUrl - for the cover image or card image etc.
  • summary - if you want to show additional summary
  • pageId - this is the most important part since it is going to allow you to fetch your page via the API. You can find the pageId at the end of your Notion Page’s link, it is a 32 character long alpha-numeric string.

After creating the table, fill in the first row with relevant information about your blogpost. Once you’re done with all that, just grant the integration you created access to this Notion database like shown here. Alright, now you’re all set to jump back into the get the ball rolling.

Retrieving the blogs metadata

First, you’ll need to initialize the Notion client, you can do that by using your integration secret (the one you would’ve created during the last step)

typescript
 const notion = new Client({
  auth: NOTION_KEY as string, // NOTION_KEY is your integration secret
});

You can use the notion.databases.query function to retrieve the metadata from the database you just created. From the API response, de-structure the results array and take out the properties you’ll need to display on your blog or use to retrieve the individual pages (blogs).

typescript
 // sorry about the lack of syntax highlighting, will add support for that soon!

const { results }: QueryDatabaseResponse = await notion.databases.query({
		// database_id can be found at the end of your database's link
    database_id: "660de912c4c942509a7956d54ea0dr13", 
    filter: { // filter by a status column or tags if you went with that
      property: 'tags',
      status: {
        equals: 'Live',
      },
    },
  });

If you’ve used a table like mine, you’ll end up with data that has the following properties:

typescript
 export type Page = {
  pageId: string;
  pageName: string;
  imageUrl: string;
  pageSummary: string;
};

Now, before we start fetching the page, we need to understand that each Notion page is made up of multiple blocks, and therefore, to fetch an individual page, you’ll need to use the Retrieve block children API to fetch all the blocks of that page where the block id, will be your page’s id. In code, it’ll be something like this.

typescript
 const { results } = await notion.blocks.children.list({
    block_id: pageId as string,
  });

Now, once you have the results (blocks) from a page comes the fun part, NotionJSX. So, you can now take all of these blocks and convert them to JSX with the blink of an eye and snap of a finger.

Using NotionJSX is super easy and in code, it will look like this.

typescript
 import notionJSX from 'notion-jsx';

const elements =  notionJSX.generateJSX(blocks) as ReactElements[]

return (
	<div>
		{elements?.map((element: ReactElement, index: number) => {
        return React.createElement(
          element.type,
          {
            key: element.key || index,
            className: element.props.className,
            ...element.props,
          },
          element.props.children
        );
      })}
	</div>
);

And BOOM! Congratulations, your very own blog is ready and you’re using Notion as a CMS for it. With that being said, I’ll wrap this very first blogpost up, looking forward to seeing your blog if you end up creating one like me. Have a good one.